#include <nxos.h>

NX_PRIVATE char * threadState[] = {
    "    INIT",
    "   READY",
    " RUNNING",
    " BLOCKED",
    "    EXIT",
};

typedef struct {
    NX_List list;
    NX_U32 tid;
    int update; /* update info */
    NX_UsageInfo usage;
} ThreadInfo;

NX_LIST_HEAD(lastThreadInfoList);

int isStarted = 0;

NX_PRIVATE NX_ClockTick LastTotalTick[NX_MAX_CPU] = {0};
NX_PRIVATE NX_ClockTick LastIdleTick[NX_MAX_CPU] = {0};
NX_PRIVATE NX_CpuUsageInfo CpuUsageInfo[NX_MAX_CPU] = {0};

NX_PRIVATE void FlushThreadInfo(void)
{
    ThreadInfo *info;
    NX_ListForEachEntry(info, &lastThreadInfoList, list)
    {
        info->update = 0;
    }
}

NX_PRIVATE ThreadInfo *SearchThreadInfo(NX_SnapshotThread *thread)
{
    ThreadInfo *info;
    NX_ListForEachEntry(info, &lastThreadInfoList, list)
    {
        if (info->tid == thread->threadId)
        {
            return info;
        }
    }
    return NX_NULL;
}

NX_PRIVATE ThreadInfo *CreateThreadInfo(NX_SnapshotThread *thread)
{
    ThreadInfo *info;
    
    info = NX_MemAlloc(sizeof(ThreadInfo));
    if (info == NX_NULL)
    {
        return NX_NULL;
    }
    
    NX_ListAdd(&info->list, &lastThreadInfoList);
    info->update = 1;
    info->tid = thread->threadId;
    info->usage = thread->usage;
    return info;
}

NX_PRIVATE void DestroyThreadInfo(ThreadInfo *info)
{
    NX_ListDel(&info->list);
    NX_MemFree(info);
}

NX_PRIVATE void CleanupThreadInfo(void)
{
    ThreadInfo *info, *next;
    NX_ListForEachEntrySafe(info, next, &lastThreadInfoList, list)
    {
        if (info->update == 0)
        {
            DestroyThreadInfo(info);
        }
    }
}

NX_PRIVATE void UpdateThreadInfo(NX_SnapshotThread *thread)
{
    ThreadInfo *info;
    info = SearchThreadInfo(thread);
    
    /* not found */
    if (info == NX_NULL)
    {
        info = CreateThreadInfo(thread);
        if (info == NX_NULL)
        {
            NX_Printf("no memory!\n");
            return;
        }
    }
    else
    {
        info->update = 1;
        info->usage = thread->usage;
    }
}

NX_PRIVATE NX_Error GetThreadSnapshot()
{
    NX_Error err = NX_EOK;
    NX_SnapshotThread snapshotInfo;
    ThreadInfo *info;

    NX_Solt solt = NX_SnapshotCreate(NX_SNAPSHOT_THREAD, 0);
    if (solt == NX_SOLT_INVALID_VALUE)
    {
        NX_Printf("Create snapshot failed!");
        return err;
    }

    if (isStarted)
    {
        NX_Printf("     TID      PID   ON_CPU  CPU_AFF    STATE FIX_PRIO     PRIO  USAGE    NAME\r\n");
        
        if ((err = NX_SnapshotFirst(solt, (void *)&snapshotInfo)) == NX_EOK)
        {
            do
            {
                /* calc thread usage and print */
                info = SearchThreadInfo(&snapshotInfo);
                if (info)
                {
                    NX_ClockTick tick1, tick2;
                    int per;

                    tick1 = info->usage.system + info->usage.user;
                    tick2 = snapshotInfo.usage.system + snapshotInfo.usage.user;
                    per = (tick2 - tick1) * 100 / (1000);

                    NX_Printf("%8d %8d %8d %8d %s %8d %8d %6d    %s\r\n", snapshotInfo.threadId, snapshotInfo.ownerProcessId,
                        snapshotInfo.onCore, snapshotInfo.coreAffinity,
                        threadState[snapshotInfo.state], snapshotInfo.fixedPriority, snapshotInfo.priority,
                        per, snapshotInfo.name);
                }
                
                UpdateThreadInfo(&snapshotInfo);
            } while((err = NX_SnapshotNext(solt, (void *)&snapshotInfo)) == NX_EOK);
        }
        /* check if no update, remove */
        CleanupThreadInfo();
        /* flush thread info */
        FlushThreadInfo();
    }
    else
    {
        /* get first time */
        if ((err = NX_SnapshotFirst(solt, (void *)&snapshotInfo)) == NX_EOK)
        {
            do
            {
                /* register thread info */
                UpdateThreadInfo(&snapshotInfo);
            } while((err = NX_SnapshotNext(solt, (void *)&snapshotInfo)) == NX_EOK);
        }
        /* flush thread info */
        FlushThreadInfo();
    }

    NX_SoltClose(solt);
    return NX_EOK;
}

NX_Error PowerBoxBuiltinMonitor(int argc, char *argv[], char * env)
{
    NX_MemInfo meminfo;
    NX_CpuInfo cpuinfo;
    NX_CpuUsageInfo *cpuUsage;
    NX_U32 cores;
    int i;
    int times = 5;

    if (argc >= 2)
    {
        times = NX_StrToUL(argv[1], NX_NULL, 10);
        if (!times)
        {
            times = 5;
        }
    }

    int memfd = NX_FileOpen("/System/Device/meminfo", NX_FILE_RDONLY, 0);
    if (memfd < 0)
    {
        NX_Printf("open meminfo faild!\n");
        return NX_ERROR;
    }

    int cpufd = NX_FileOpen("/System/Device/cpuinfo", NX_FILE_RDONLY, 0);
    if (cpufd < 0)
    {
        NX_Printf("open cpuinfo faild!\n");
        return NX_ERROR;
    }

    /* get info from kernel */
    NX_FileRead(cpufd, &cpuinfo, sizeof(NX_CpuInfo));
    cores = cpuinfo.cores;

    while (times-- > 0)
    {
        NX_FileRead(memfd, &meminfo, sizeof(meminfo));

        if (isStarted)
        {
            NX_Printf("\e[1;1H\e[2J");
            NX_Printf("--------------------------------------------------------------------------------------\n");
            NX_Printf(" NXOS Resource Monitor                            \n");
            NX_Printf("--------------------------------------------------------------------------------------\n\n");            
        }

        for (i = 0; i < cores; i++)
        {
            char cpuName[32] = {0};
            NX_StrPrintfN(cpuName, 32, "/System/Device/cpustat%d", i);
            int cpustat = NX_FileOpen(cpuName, NX_FILE_RDONLY, 0);
            if (cpustat < 0)
            {
                NX_Printf("open cpustat faild!\n");
                continue;
            }
            cpuUsage = &CpuUsageInfo[i];
            NX_FileRead(cpustat, cpuUsage, sizeof(NX_CpuUsageInfo));

            if (isStarted)
            {
                
            }

            NX_ClockTick total = cpuUsage->user + cpuUsage->system + 
                cpuUsage->delayirq + cpuUsage->irq + cpuUsage->idle + 
                cpuUsage->iowait;
            int per;
            int totalDelta = 0, idleDelta = 0;

            if (LastTotalTick[i])
            {
                totalDelta = total - LastTotalTick[i];
            }
            LastTotalTick[i] = total;
            
            if (LastIdleTick[i])
            {
                idleDelta = cpuUsage->idle - LastIdleTick[i];
            }
            LastIdleTick[i] = cpuUsage->idle;
            
            if (!idleDelta && !totalDelta)
            {
                per = 0;
            }
            else
            {
                per = 100 - (idleDelta * 100 / totalDelta);
            }

            if (isStarted)
            {
                NX_Printf("Core: %d     User: %d System: %d DelayIrq: %d Irq: %d Idle: %d Iowait: %d\n", i, 
                    cpuUsage->user, cpuUsage->system, 
                    cpuUsage->delayirq, cpuUsage->irq, 
                    cpuUsage->idle, cpuUsage->iowait);

                NX_Printf("[\x1b[31m");
                int tens = (per + 1) / 2;
                int i = 0;
                for (i = 0; i < tens; i++)
                {
                    NX_Printf("|");
                }
                for (; i < 50; i++)
                {
                    NX_Printf(" ");
                }

                NX_Printf("\x1b[0m] %d%%\n", per);
                NX_Printf("\n");
            }
            NX_FileClose(cpustat);
        }

        NX_Size usedPage = meminfo.usedPage + (meminfo.memPage - meminfo.totalPage);
        NX_Size usedMem = usedPage * meminfo.pageSize;
        
        if (isStarted)
        {
            NX_Printf("Memory\n");
            NX_Printf("Total :   %d MB\n", (meminfo.memPage * meminfo.pageSize) / (1024 * 1024));
            NX_Printf("Paged :   %d MB\n", (meminfo.totalPage * meminfo.pageSize) / (1024 * 1024));
            NX_Printf("Free  :   %d MB\n", ((meminfo.memPage - usedPage) * meminfo.pageSize) /(1024 * 1024));
            int usePer = (usedMem * 100) / (meminfo.memPage * meminfo.pageSize);
            NX_Printf("[\x1b[31m");
            int tens = (usePer + 1) / 2;

            for (i = 0; i < tens; i++)
            {
                NX_Printf("|");
            }
            for (; i < 50; i++)
            {
                NX_Printf(" ");
            }

            NX_Printf("\x1b[0m] %d%%\n", usePer);
            NX_Printf("\n");
            NX_Printf("--------------------------------------------------------------------------------------\n");            
        }

        GetThreadSnapshot();
        if (isStarted)
        {
            NX_Printf("--------------------------------------------------------------------------------------\n\n");
        }
        isStarted = 1;
        
        NX_ThreadSleep(1000);
    }
    
    NX_FileClose(memfd);
    NX_FileClose(cpufd);

    CleanupThreadInfo();
    isStarted = 0;

    return NX_EOK;
}
